home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 22 / Cream of the Crop 22.iso / program / ctlib100.zip / INSTALL.LZH / GRAPHS.TXT < prev    next >
Text File  |  1996-10-12  |  20KB  |  387 lines

  1.                         CHAPTER 4
  2.                    Non-linear containers
  3.  
  4.  
  5. This chapter describes the complete interface of TGraph, the base object for all non-linear containers in the Containers Library object hierarchy.  All non-linear containers are ultimately derived from TGraph, so the topics discussed in this chapter apply to all non-linear containers in the library.
  6.  
  7. In this chapter you will learn how to:
  8.  
  9.   - Use keys to identify items in a container
  10.   - Retrieve data from a container using keys
  11.   - Make comparisons of keys case-sensitive or case-insensitive
  12.   - Allow duplicates in containers
  13.   - Restrict the scope of some methods using ExactMatch
  14.   - Move through items in a non-linear container
  15.   - Use iterative methods for finding items and conditionally 
  16.     moving through a non-linear container
  17.   - Determine the number of items that satisfy certain search
  18.     criteria
  19.  
  20.  
  21. Using keys
  22.  
  23. A key is something that identifies a particular data item in a data set.  Keys can be of any type.  For example, a key could be the last name of a client, a person's social security number or perhaps a combination of both.
  24.  
  25. Graphs (or non-linear containers) are key-based containers.  This means that all items in a graph must have a key, and that the keys of all items in the same graph must be of the same type.  Graphs use these keys to sort the items in the container, and then provide indexed access to your data.  In other words, they allow you to retrieve an item simply by providing the key of the item that you are looking for.
  26.  
  27. As an example, let's assume that you want to provide indexed access to a set of TClient records that have the following structure:
  28.  
  29.   PClient = ^TClient;
  30.   TClient = record
  31.     ClientNo : LongInt;
  32.     LastName : string[40];
  33.     FirstName : string[20];
  34.     MiddleI : Char;
  35.   end;
  36.  
  37. We will use ClientNo as the key of our data items.
  38.  
  39. To use a graph to store a set of TClient records, the first thing you must do is teach the graph how to retrieve the key of a data item in the container.  This is done by overriding the KeyOf method in TGraph.  In our case, KeyOf must return a pointer to the ClientNo field of TClient:
  40.  
  41.   TMyGraph = object(TB+Tree)
  42.     function KeyOf(Item: Pointer): Pointer; virtual;
  43.   end;
  44.  
  45.   TMyGraph.KeyOf(Item: Pointer): Pointer;
  46.   begin
  47.     KeyOf := @(PClient(Item)^.ClientNo);
  48.   end;
  49.  
  50. By default, TGraph assumes that keys of items in the container are of type string (or PString if working in a Windows application).  However, if you are using keys of another type (as in our example) you must also teach the graph how to compare the keys of two different data items.  This is done by overriding the Compare method:
  51.  
  52.   TMyGraph = object(TB+Tree)
  53.     function Compare(Key1, Key2 : Pointer):  Integer; virtual;
  54.   end;
  55.  
  56.   function TMyGraph.Compare(Key1,  Key2 : Pointer): Integer;
  57.   begin
  58.     if LongInt(Key1)^ < LongInt(Key2)^
  59.       then Compare := -1
  60.     else if LongInt(Key1)^ = LongInt(Key2)^
  61.       then Compare := 0
  62.     else Compare := 1;
  63.   end;
  64.  
  65. Compare must return a result as follows:
  66.  
  67.   -1   if Key1 < Key2
  68.    0   if Key1 = Key2
  69.    1   if Key1 > Key2
  70.  
  71. Once the container knows how to extract the key of an item and how to compare the keys of two different items, you are ready to start adding data to the graph.  In the following sections you will learn how to find and retrieve data from a graph, using the keys of items in the graph.
  72.  
  73.  
  74. Retrieving data from a graph
  75.  
  76. Retrieving data from a graph is as easy as indicating the key of the item that you want to retrieve.  TGraph provides two methods for retrieving data: KeyFirst and KeyLast.  
  77.  
  78.  
  79. KeyFirst and KeyLast
  80.  
  81. KeyFirst returns the first occurrence of an item with a key that matches the key you specify.  For example,
  82.  
  83.   function FindFirstExact (Key: Pointer): Pointer;
  84.   begin
  85.     MyGraph^.ExactMatch := True;
  86.     FindFirstExact := MyGraph^.KeyFirst(Key);
  87.   end;
  88.  
  89. The ExactMatch field indicates TGraph to return an item only if the item has exactly the same key as the one specified.  ExactMatch is explained in more detail later in this section.
  90.  
  91. KeyLast returns the last occurrence of an item with a key that matches the key you specify.  For example:
  92.  
  93.   function FindLastExact(Key : Pointer): Pointer;
  94.   begin
  95.     MyGraph^.ExactMatch := True;
  96.     FindLastExact := MyGraph^.KeyLast(Key);
  97.   end;
  98.  
  99. If no duplicates are allowed in the container, KeyLast is equivalent to KeyFirst.  Additional information on allowing duplicates in a graph can be found later in this chapter.
  100.  
  101.  
  102. ExactMatch
  103.  
  104. ExactMatch is a boolean field that allows you to restrict the scope of some methods to only items with a particular key.  When ExactMatch is True, searches will only return an item if the item matches exactly the key that you specified.  If ExactMatch is False, searches will either return an item with a key that matches exactly the key you specified or, if not found, the next higher or lower item found.  For example, the following code will either return the first item with a key of 'Mark', or return an item with the next higher key (e.g., 'Markus').
  105.  
  106.   function FindFirstNearest;
  107.   var
  108.     Key : string;
  109.   begin
  110.     MyGraph^.ExactMatch := False;
  111.     Key := 'Mark';
  112.     FindFirstNearest := MyGraph^.KeyFirst(@Key);
  113.   end;
  114.  
  115. If we used KeyLast instaed, it would return the first item with a key lower than the key you specified if an exact match is not found:
  116.  
  117.   function FindLastNearest;
  118.   var
  119.     Key : string;
  120.   begin
  121.     MyGraph^.ExactMatch := False;
  122.     Key := 'Markus';
  123.     FindLastNearest := MyGraph^.KeyLast(@Key);
  124.   end;
  125.  
  126. All methods in TGraph that take a key as a parameter are relative to ExactMatch.  Throughout the rest of this chapter, more information on how ExactMatch affects the behavior of each method is provided after the function of the respective method has been explained.
  127.  
  128. Note:  Since ExactMatch can be constantly changing, we recommend that you always set the value of ExactMatch to the appropriate value before calling a TGraph method.  This is not necessary, however, if you never change the value of ExactMatch.  ExactMatch is by default True.
  129.  
  130.  
  131. The CaseSensitive field
  132.  
  133. When a TGraph descendant searches for an item with a given key, it compares the key of each item with the key it is looking for.  By default, all comparisons are sensitive to the case of the keys;  that is, all comparisons of keys in TGraph descendants are case-sensitive.  In some situations, however, you may want TGraph to ignore the case of keys when looking for an item.  You can control this by changing the value of the CaseSensitive field.
  134.  
  135. CaseSensitive tells TGraph whether key comparisons are case-sensitive or not.  If CaseSensitive is True, uppercase letters are considered to be different and smaller to their lowercase equivalents.  Otherwise, uppercase letters are considered to be equal to their lowercase equivalents.  For example:
  136.  
  137.   CaseSensitive = True,  'A' < 'a'
  138.   CaseSensitive = False, 'A' = 'a'
  139.  
  140. When changing the value of CaseSensitive, remember to change it immediately after creating the container.  Otherwise, items inserted in the container before CaseSensitive was changed, will not be sorted correctly.
  141.  
  142.  
  143. Allowing duplicate keys
  144.  
  145. Sometimes you will need to store different items in a container that have the same key.  These two items have duplicate keys and are said to be duplicate items, despite the fact that they cannot be identical in all aspects.  By default, TGraph descendants do not allow the insertion of duplicate items.  If you try to insert an item with a key that matches the key of an item already in the graph, the container will abort the operation and report a "duplicate key" error.
  146.  
  147. You can insert duplicate items by changing the value of the Duplicates field in TGraph.  Duplicates determines whether the container accepts items with duplicate keys.  When Duplicates is False, the container will only accept the first item inserted with a given key.  When Duplicates is True, the container will insert duplicate items immediately before the first existing item with the same key.  This way, you will always find first the newest item first when performing a search in the container.
  148.  
  149. Warning!  After setting Duplicates to True, you should never set it back to False unless you are certain there are no duplicate items in the container.  Otherwise, the container will not be able to correctly structure the data or in most cases even access correctly the data already inserted.
  150.  
  151.  
  152. Updating and replacing data
  153.  
  154. TGraph allows you to update and replace data by means of the ItemUpdate and ItemReplace methods.  These methods take two parameters: a pointer to the old item that must be replaced and a pointer to the new item that will be inserted in its place.  When called, they will search for the old item in the container and if found, proceed to replace or update it with the new item.
  155.  
  156. ItemPut replaces an item, but does not disposes of the item being replaced.  It is equivalent to:
  157.  
  158.   Delete(OldItem);
  159.   Insert(NewItem);
  160.  
  161. After the new item is inserted, you must manually free the item by calling the container's FreeItem method.  ItemReplace, on the other hand, will both delete and dispose of the item being replaced.
  162.  
  163.   NewItem := NewItem;
  164.   Key := 'Keaton';
  165.   OldItem := MyGraph^.KeyFirst(@Key);
  166.   NewItem^.Key := OldItem^.Key;
  167.   {... add new information to NewItem ...}
  168.   MyGraph^.ItemReplace(OldItem, NewItem);
  169.  
  170. Be aware that in graphs you can't change the key of an item in the container simply by changing the value of the key field in the item.  To keep the integrity of the key order in a graph, you must either delete the item, change the item's key and then reinsert it, or create a new item, fill the fields of the new item with the appropriate data and the new key and then use ItemPut to insert the item.
  171.  
  172. Important!  ItemReplace will insert the new item in exactly the same position in the graph where the old item is stored.  Therefore, you must only use this method if both items have the same key, or if you are absolutely sure that the key order in the container will not be corrupted by the new insertion.  If the key of both items is different, you should use ItemPut instead.
  173.  
  174.  
  175. Moving in a graph
  176.  
  177. TGraph implements a series of methods that will allow you to move through the items a in graph, one item at a time.  These methods are First, Next, Last and Prev.  These methods differ from their TSequence equivalents in the type of seed values they use.  In TGraph, methods use data items as their seed values.  TSequence methods, on the other hand, use the index number of items as their seed values.
  178.  
  179.  
  180. First
  181.  
  182. First returns the first item in key order stored in a graph. Note that this is not necessarily the first item that you inserted.
  183.  
  184.   Item := MyGraph^.First;
  185.  
  186. The item returned by First can be used as a seed value in other methods like Next or Prev.  Usually you will use First together with Next or Prev to move through the items in a container.
  187.  
  188.  
  189. Next
  190.  
  191. Next takes one parameter, Item, and returns a pointer to the item following Item in key order.  
  192.  
  193.   with MyGraph^ do
  194.   begin
  195.     ExactMatch := False;
  196.     if Status > ctOk
  197.       then Status := ctOk;
  198.     Item := First;
  199.     while Status = ctOk do
  200.     begin
  201.       {...do something with item...}
  202.       Item := Next(Item);
  203.     end;
  204.   end;
  205.  
  206. The item returned by Next can be used as a seed value in other methods (e.g. Prev) or in another call to Next.
  207.  
  208. This method is relative to ExactMatch.  If ExactMatch is True, Next will only return the next item if it has the same key as Item.  Otherwise, if ExactMatch is False, Next will always return the next item regardless of its key.  In the previous example, we needed to set ExactMatch to False to be able to visit all items in the graph.  In the next example, however, we will set ExactMatch to True so that we visit only those items that have a particular key (in this case, 'Allen'):
  209.  
  210.   with MyGraph^ do
  211.   begin
  212.     ExactMatch := False;
  213.     Key := 'Allen';
  214.     if Status > ctOk
  215.       then Status := ctOk;
  216.     Item := KeyFirst(@Key);
  217.     while Status = ctOk do
  218.     begin
  219.       {...do something with item...}
  220.       Item := Next(Item);
  221.     end;
  222.   end;
  223.  
  224. Since ExactMatch is used by other methods to behave in different ways, in most situations it is necessary to set ExactMatch to the appropriate value before calling this method.
  225.  
  226.  
  227. Last
  228.  
  229. Last returns the last item in key order stored in a graph.  Note that this is not necessarily the last item you inserted.
  230.  
  231.   Item := MyGraph^.Last;
  232.  
  233. The item returned by Last can be used as a seed value in other methods like Prev or Next.  Usually you will use Last together with Prev or Next to move through the items in a container.
  234.  
  235.  
  236. Prev
  237.  
  238. Prev takes one parameter, Item, and returns a pointer to the item preceding Item in key order.
  239.  
  240.   with MyGraph^ do
  241.   begin
  242.     ExactMatch := False;
  243.     if Status > ctOk
  244.       then Status := ctOk;
  245.     Item := Last;
  246.     while Status = ctOk do
  247.     begin
  248.       {...do something with item...}
  249.       Item := Prev(Item);
  250.     end;
  251.   end;
  252.  
  253. The item returned by Prev can be used as a seed value in other methods (e.g. Next) or in another call to Prev.
  254.  
  255. This method is relative to ExactMatch.  If ExactMatch is True, Prev will only return the preceding item if it has the same key as Item.  Otherwise, if ExactMatch is False, Prev will always return the preceding item regardless of its key.  For example, in the following code we set ExactMatch to True so that we visit only those items that have a particular key (in this case, 'West'):
  256.  
  257.   with MyGraph^ do
  258.   begin
  259.     ExactMatch := False;
  260.     Key := 'West';
  261.     if Status > ctOk
  262.       then Status := ctOk;
  263.     Item := KeyLast(@Key);
  264.     while Status = ctOk do
  265.     begin
  266.       {...do something with item...}
  267.       Item := Prev(Item);
  268.     end;
  269.   end;
  270.  
  271. Since ExactMatch is used by other methods to behave in different ways, in most situations it is necessary to set ExactMatch to the appropriate value before calling this method.
  272.  
  273.  
  274. Iterative methods
  275.  
  276. Additional iterative methods in TGraph allow you to quickly find any item in a graph or conditionally move through all the items stored in it.  These iterative methods work just like First, KeyFirst, Next, Prev, Last and KeyLast, but allow you to filter the items that are actually visited in a search.
  277.  
  278.  
  279. The FirstThat and LastThat iterators
  280.  
  281. FirstThat and LastThat iterators provide an easy way of finding items in a container.  FirstThat returns the first item in a container that satisfies a search criterion while LastThat returns the last item in a container that satisfies a search criterion.
  282.  
  283. These iterators take a pointer to a far local boolean function of type TTestFunction and return an item accordingly.  This item can then be used as a seed value (or starting point) in other methods like NextThat or PrevThat.
  284.  
  285.   procedure FindWinners;
  286.  
  287.     function IsWinner(Item : PMyObject): Boolean; far;
  288.     begin
  289.       IsWinner := Item^.LotteryNumber := '2736491';
  290.     end;
  291.  
  292.   begin
  293.     with MyGraph^ do
  294.     begin
  295.       if Status > ctOk
  296.         then Status := ctOk;
  297.       Item := FirstThat(@IsWinner);
  298.       while Status = ctOk do
  299.       begin
  300.         DisplayWinner(Item);
  301.         Item := NextThat(@IsWinner, Item);
  302.       end; 
  303.     end;
  304.   end;
  305.  
  306. Remember to be careful about what sort of functions you call with iterators.  Remember that the functions cannot be an object's methods and that it must be local to (nested in the same block with) the routine that is calling it.  Functions must also be declared as far functions, either with the far directive or with the $F+ compiler directive.  Finally, the functions must take a pointer to a container item as their only parameter.
  307.  
  308.  
  309. KeyFirstThat and KeyLastThat
  310.  
  311. The KeyFirstThat and KeyLastThat iterators are FirstThat and LastThat equivalents that let you find items in a graph that have an specific key and also satisfy a search criterion.  These methods are relative to ExactMatch.  If ExactMatch is True, they will return not return an item unless it matches exactly the key provided.  Otherwise, if ExactMatch is False, KeyFirstThat will return the next higher item that satisfies Test and KeyLastThat will return the next lower item that satisfies Test.
  312.  
  313. Since ExactMatch is used by other methods to behave in different ways, in most situations it is necessary to set ExactMatch to the appropriate value before calling this method.
  314.  
  315.  
  316. The NextThat and PrevThat methods
  317.  
  318. When used together with FirstThat and LastThat, the NextThat and PrevThat iterators provide a way of conditionally moving through a container.  That is, the let you select or filter the items that you want to visit when moving in a container.
  319.  
  320. These iterators take two parameters:  a pointer to a far local boolean function of type TTestFunction and the item where you want to start the search in the container.  The item returned can then be used as a seed value (or starting point) in other calls to NextThat and PrevThat.
  321.  
  322.   procedure DisplayReversePrimes;
  323.  
  324.     function IsPrime(Item : PMyObject) : Boolean; far;
  325.     begin
  326.       IsPrime := NumberIsPrime(Item^.Value);
  327.     end;
  328.  
  329.   begin
  330.     with MyGraph^ do
  331.     begin
  332.       if Status > ctOk
  333.         then Status := ctOk;
  334.       Item := LastThat(@IsPrime, Index);
  335.       while Status = ctOk do
  336.       begin
  337.         Display(Item);
  338.         Item := PrevThat(@IsPrime, Item);
  339.       end;
  340.     end;
  341.   end;
  342.  
  343. NextThat and PrevThat are relative to ExactMatch.  This means that if ExactMatch is True, they won't return an item unless it has exactly the same key as the key of the item passed as a seed value. Otherwise, if ExactMatch is False, they will return the item following or preceding the item passed as a seed value, regardless of the key of the seed item.  For example, the following code will visit only those items with key 'Johnson' and that also match the condition given by Test:
  344.  
  345.   with MyGraph^ do
  346.   begin
  347.     ExactMatch := True;
  348.     Key := 'Johnson';
  349.     if Status > ctOk
  350.       then Status := ctOk;
  351.     Item := KeyFirstThat(@Test, @Key);
  352.     while Status = ctOk do
  353.     begin
  354.       Display(Item);
  355.       Item := NextThat(@Test, Item);
  356.     end;
  357.   end;
  358.  
  359. The first call to either NextThat or PrevThat should always be preceded by a call to First, FirstThat, KeyFirstThat, Last, LastThat or KeyLastThat.. The function of these methods is to set the starting point of new searches, though this is not necessary if you already have a pointer to the item where you want to begin your search.
  360.  
  361.  
  362. Find and FindThat
  363.  
  364. You can easily count the number of items with a certain key in a container or the number of those items that also match a search criteria, by using the Find and FindThat iterators.
  365.  
  366. Find takes a parameter to the key you want to look for, and returns in the Hits parameter the number of occurrences found.  For example:
  367.  
  368.   Key := 'Smith';
  369.   MyGraph^.Find(@Key, Hits);
  370.  
  371. FindThat searches in the container for all items with a key that matches the key you provide and that also a satisfy condition, given by the Test parameter which must be a pointer to a far local boolean function of type TTestFunction.
  372.  
  373.   function CountChildren(LastName: string): LongInt;
  374.   var
  375.     Hits : LongInt;
  376.  
  377.     function IsChildren(Item: PMyObject): Boolean; far;
  378.     begin
  379.       IsChildren := Item^.Age < 10;
  380.     end;
  381.  
  382.   begin
  383.     MyGraph^.FindThat(@LastName, @IsChildren, Hits);
  384.     CountChildren := Hits;
  385.   end;
  386.  
  387. The value of ExactMatch has no effect on the result of the Find or FindThat methods.